﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.VisualStudio.Modeling.Diagrams;
using Microsoft.VisualStudio.Modeling;
using System.Diagnostics;
using System.Windows.Forms;
using System.IO;

namespace DSLFactory.VSCTDesigner
{
    partial class VSCTDesignerDiagram
    {
        private const double shapeMarginY = 0.1;
        private const double shapeMarginX = 0.2;

        /// <summary>
        /// Called when [initialize].
        /// </summary>
        public override void OnInitialize()
        {
            base.OnInitialize();
            this.SnapToGrid = false;
            this.RouteJumpType = Microsoft.VisualStudio.Modeling.Diagrams.GraphObject.VGPageLineJumpCode.NoJumps;
        }

        /// <summary>
        /// Called when [child configuring].
        /// </summary>
        /// <param name="child">The child.</param>
        /// <param name="createdDuringViewFixup">if set to <c>true</c> [created during view fixup].</param>
        protected override void OnChildConfiguring(ShapeElement child, bool createdDuringViewFixup)
        {
            // On s'assure que les bitmaps sont toujours avant
            if( child is BitmapsSwimlane )
            {
                int bx = this.NestedChildShapes.IndexOf(child);
                int cx = FindCommandsSwimlane();

                if( bx > cx && cx >= 0 )
                {
                    this.NestedChildShapes.Move(bx, cx);
                    SwimlaneHelper.AnchorAllSwimLanes(this);
                }
            }
        
            base.OnChildConfiguring(child, createdDuringViewFixup);
        }

        /// <summary>
        /// Finds the commands swimlane.
        /// </summary>
        /// <returns></returns>
        private int FindCommandsSwimlane()
        {
            int cx = -1;
            // Recherche de CommandsSwimlane
            for( int i = 0; i < this.NestedChildShapes.Count; i++ )
            {
                if( NestedChildShapes[i] is CommandsSwimLane )
                {
                    cx = i;
                    break;
                }
            }
            return cx;
        }

        /// <summary>
        /// Alignement horizontal
        /// </summary>
        internal void PerformLayout()
        {
            List<Guid> shapesPositionned = new List<Guid>();

            this.Store.RuleManager.DisableRule(typeof(CommandShapeLayoutRule));
            this.Store.RuleManager.DisableRule(typeof(ParentLinkLayoutRule));

            try
            {
                using( Transaction transaction = this.Store.TransactionManager.BeginTransaction("Layout") )
                {
                    int ix = FindCommandsSwimlane();
                    Debug.Assert(ix != -1);

                    ShapeElement commandsShape = this.NestedChildShapes[ix];
                    List<ShapeElement> containers = new List<ShapeElement>();

                    // Recherche de toutes les container de plus haut niveau
                    foreach( ShapeElement shape in commandsShape.NestedChildShapes )
                    {
                        if( shape is MenuShape || shape is GroupShape || shape is ExternCommandShape )
                        {
                            if( shape.IsVisible )
                            {
                                Command cmd = shape.Subject as Command;
                                if( cmd != null && cmd.Parents.Count == 0 )
                                {
                                    containers.Add(shape);
                                }
                            }
                        }
                    }

                    // Puis on les positionne
                    double xMenuPos = shapeMarginX;
                    int nbVisibleShapes = containers.Count;
                    bool onlyOneRow = true;
                    double yMenuPos = shapeMarginY;
                    double nextRowX = shapeMarginX;
                    foreach( ShapeElement shape in containers )
                    {
                        if( onlyOneRow )
                        {
                            yMenuPos = shapeMarginX;
                            xMenuPos = nextRowX;
                        }
                        
                        if(nbVisibleShapes > 4)
                            onlyOneRow = !onlyOneRow;                        

                        shapesPositionned.Add(shape.Id);

                        NodeShape menuShape = shape as NodeShape;
                        ShapeHelper.ResizeToContent(menuShape);
                        menuShape.Location = new PointD(xMenuPos, yMenuPos);

                        // Positionnement de ses items
                        yMenuPos += shape.BoundingBox.Height + shapeMarginY * 3;

                        nextRowX = Math.Max(nextRowX, ChildLayoutDefinition(menuShape, xMenuPos+shapeMarginX*2, ref yMenuPos, shapesPositionned) + shapeMarginX);
                    }

                    // Commandes orphelines à positionner
                    yMenuPos = shapeMarginY;
                    xMenuPos = nextRowX;
                    foreach( ShapeElement shape in commandsShape.NestedChildShapes )
                    {
                        if( shapesPositionned.Contains(shape.Id) )
                            continue;

                        NodeShape menuShape = shape as NodeShape;
                        if( menuShape != null && menuShape.IsVisible)
                        {
                            ShapeHelper.ResizeToContent(menuShape);
                            menuShape.Location = new PointD(xMenuPos, yMenuPos);
                            xMenuPos += shape.BoundingBox.Width + shapeMarginX;
                            yMenuPos += shape.BoundingBox.Height + shapeMarginY;
                        }
                    }
                    transaction.Commit();
                }
            }
            finally
            {
                this.Store.RuleManager.EnableRule(typeof(ParentLinkLayoutRule));
                this.Store.RuleManager.EnableRule(typeof(CommandShapeLayoutRule));
            }

            this.Invalidate(true);
            this.Reroute();
        }

        /// <summary>
        /// Retourne la position occupée la + à droite
        /// </summary>
        /// <param name="pel"></param>
        /// <param name="xPos"></param>
        /// <param name="yPos"></param>
        /// <param name="shapesAlreadyPositionned"></param>
        /// <returns></returns>
        private double ChildLayoutDefinition(NodeShape shape, double xPos, ref double yPos, List<Guid> shapesAlreadyPositionned)
        {
            Command mel = shape.Subject as Command;
            System.Diagnostics.Debug.Assert(mel != null);

            double righterPos = shape.BoundingBox.Width + xPos;
            // Positionnement des enfants
            // Se fait en 2 passages :
            //  - Recueil des shapes à positionner et ajustement de leurs tailles
            //  - Positionnement des shapes recueillis avec ajustement à la taille su hape le plus large
            List<NodeShape> shapes = new List<NodeShape>();
            double maxWidth = 0.0;
            foreach( Command child in mel.Childs )
            {
                PresentationElement pel = null;
                ModelElement parentShape = null;
                // Recherche si le shape correspondant est déjà positionné
                foreach( PresentationElement sh in PresentationViewsSubject.GetPresentation(child) )
                {
                    parentShape = ( (NodeShape)sh ).ParentShape;
                    if( !shapesAlreadyPositionned.Contains(sh.Id) )
                    {
                        pel = sh;
                        break;
                    }
                }

                // Si pel==null, c'est que tous les shapes associés sont dèjà positionnés (ce cas arrive lors
                // du premier positionnement si l'élément est fils de plusieurs parents)
                NodeShape nodeShape = pel as NodeShape;
                if( pel == null )
                {
                    // Création d'un nouveau shape associé
                    ShapeElement childShape = CreateChildShape(child);
                    childShape.Associate(child);
                    nodeShape = childShape as NodeShape;
                    ( (ShapeElement)parentShape ).NestedChildShapes.Add(childShape);

                    CommandHasParent commandHasParent = CommandHasParent.GetLink(child, mel);
                    while(true)
                    {
                        PresentationElement iter = PresentationViewsSubject.GetPresentation(commandHasParent)
                            .Where(p => p is ParentLink && ((ParentLink)p).ToShape.Id == shape.Id ).ToList().FirstOrDefault();
                        if( iter == null )
                            break;                        
                        iter.Delete();
                    }

                    ParentLink link = new ParentLink(childShape.Store, null);
                    link.Connect(nodeShape, shape);
                    link.Associate(commandHasParent);
                    ( (ShapeElement)parentShape ).NestedChildShapes.Add(link);

                    this.OnChildConfiguring(nodeShape, true);
                }

                if( nodeShape != null )
                {
                    shapes.Add(nodeShape);
                    ShapeHelper.ResizeToContent(nodeShape);
                    if( maxWidth < nodeShape.AbsoluteBoundingBox.Width )
                        maxWidth = nodeShape.AbsoluteBoundingBox.Width;
                }
            }

            // 2ème passage - Positionnement
            foreach( var nodeShape in shapes)
            {
                shapesAlreadyPositionned.Add(nodeShape.Id);
                if( nodeShape.Diagram == this && nodeShape.IsVisible )
                {
                    nodeShape.Bounds = new RectangleD(xPos, yPos, maxWidth, nodeShape.Bounds.Size.Height);
                    yPos += nodeShape.BoundingBox.Height + shapeMarginY;
                    righterPos = Math.Max(righterPos, ChildLayoutDefinition(nodeShape, xPos + shapeMarginX*2, ref yPos, shapesAlreadyPositionned));
                }
            }

            return righterPos;
        }
    }

    partial class VSCTDesignerDiagramBase
    {        
        private CommandWithIconShape CreateShapeForCommandWithIcon(CommandWithIcon commandWithIcon)
        {
            throw new System.NotImplementedException();
        }
    }

    partial class FixUpDiagram
    {
        /// <summary>
        /// Nécessaire car les DSL génére cette méthode par erreur pour une classe abstraite
        /// </summary>
        /// <param name="commandWithIcon"></param>
        /// <returns></returns>
        private Microsoft.VisualStudio.Modeling.ModelElement GetParentForCommandWithIcon(CommandWithIcon commandWithIcon)
        {
            throw new System.NotImplementedException();
        }        
    }
}
